/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.server.servlet.book.provider;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import javax.jcr.Item;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import offset.nodes.Constants;
import offset.nodes.client.model.HttpUtils;
import offset.nodes.server.servlet.book.BookInfo;

/**
 * Checks, if the hyperlink refers to a page in the book.
 * 
 * @author Walter L�tgenau
 */
public class NodeProvider {

    Session session;
    Node bookRoot;
    Node root;
    HashMap<String, String> nodeReferences = new HashMap<String, String>();
    List<String> nodeTypesWithTemplates = null;

    public NodeProvider(Session session, String path) throws RepositoryException {
        this.session = session;

        bookRoot = getNode(path);
    }

    /**
     * A hyperlink, that is part of the book, will usually be a descendant of the root node.
     *
     * Note, that the book root node is the parent of the path node in case of
     * an nodes:htmlDiv.
     * 
     * @param href
     * @return
     * @throws RepositoryException
     */
    public boolean isRootDescendant(String href) throws RepositoryException {
        Node node = getNode(href);
        if (node == null)
            return false;

        if (node.getDepth() < bookRoot.getDepth())
            return false;

        Item ancestor = node.getAncestor(bookRoot.getDepth());

        if (!ancestor.isSame(bookRoot))
            return false;

         return true;
    }

    /**
     * Does the href refer to a node in the repository?
     * 
     * @param href
     * @return
     * @throws RepositoryException
     */
    public boolean isRepositoryNode(String href) throws RepositoryException {
        return getNode(href) != null;
    }

    /**
     * Return a unique reference to the node. If another reference
     * already references the same node, return this.
     * 
     * @param href
     * @return
     * @throws RepositoryException
     */
    public String getReference(String href) throws RepositoryException {
        Node node = getNode(href);
        if (node == null)
            return href;
        String reference = nodeReferences.get(node.getPath());
        if (reference != null)
            return reference;

        nodeReferences.put(node.getPath(), href);
        return href;
    }

    /**
     * Return the node pointed to by the href of the corresponding hyperlink. The href
     * may either be a path or a query with either parameter ref or reference.
     * 
     * @param href
     * @return
     * @throws RepositoryException
     */
    public Node getNode(String href) throws RepositoryException {
        if (href == null || href.length() == 0)
            return null;

        // 1. href with query
        HashMap<String, String> parameters = HttpUtils.getParameters(href);
        if (parameters.size() > 0) {
            if (parameters.get(Constants.PAR_NODE_REFERENCE) != null)
                return session.getNodeByUUID(parameters.get(Constants.PAR_NODE_REFERENCE));

            if (parameters.get(Constants.PAR_DATA_REFERENCE) != null)
                return session.getNodeByUUID(parameters.get(Constants.PAR_DATA_REFERENCE));
        }

        // 2. href with path
        URL url = null;
        try {
            url = new URL(href);
        } catch (MalformedURLException mue) {
            // 2.1. href with repository path. I.e., only the part of the path relative to the repository root.
            return getNodeByPath(href);
        }

        // 2.2. a full URL path, i.e. including the host etc.
        String query = url.getQuery();

        if (query == null) { // href with URL path
            String path = url.getPath();
            String servlet = Constants.PATH_SEPARATOR + Constants.CONTEXTMAPPING_NODES + Constants.PATH_SEPARATOR + Constants.SERVLETMAPPING_REPOSITORY;
            if (!path.startsWith(servlet))
                return null;
            path = path.substring(servlet.length() + 1);
            return getNodeByPath(path);
        }

        // a query, but not as expected in 1.
        return null;
    }

    /**
     * Return the node referred to by its repository path
     * 
     * @param path
     * @return
     */
    protected Node getNodeByPath(String path) throws RepositoryException {
        path = path.replaceAll("\\+", " ");
        if (path.startsWith(Constants.PATH_SEPARATOR))
            path = path.substring(1);
        Node root = session.getRootNode();
        if (!root.hasNode(path))
            return null;
        return root.getNode(path);
    }

    public BookInfo getBookInfo(String href) throws RepositoryException {
        Node node = getNode(href);
        if (node == null)
            return null;

        BookInfo info = new BookInfo();
        info.setId(href);
        info.setName(node.getName());
        if (node.hasProperty(Constants.PROP_CREATOR))
            info.setCreator(node.getProperty(Constants.PROP_CREATOR).getString());
        if (node.hasProperty(Constants.PROP_CREATION_USER_ID))
            info.setCreator(node.getProperty(Constants.PROP_CREATION_USER_ID).getString());
        if (node.hasProperty(Constants.PROP_CREATION_TIMESTAMP))
            info.setCreation(node.getProperty(Constants.PROP_CREATION_TIMESTAMP).getDate());
        if (node.hasProperty(Constants.PROP_MODIFICATION_USER_ID))
            info.setModifier(node.getProperty(Constants.PROP_MODIFICATION_USER_ID).getString());
        if (node.hasProperty(Constants.PROP_MODIFICATION_TIMESTAMP))
            info.setModification(node.getProperty(Constants.PROP_MODIFICATION_TIMESTAMP).getDate());
        if (node.isNodeType(Constants.TYPE_VERSIONABLE))
            info.setVersion(node.getProperty(Constants.PROP_JCR_BASE_VERION).getNode().getName());

        return info;
    }

    /**
     * Is there a template for the node type represented by the page?
     *
     * @param node the node of the page
     * @return is there a template?
     * @throws RepositoryException
     */
    protected boolean hasTemplate(Node node) throws RepositoryException {
        if (nodeTypesWithTemplates == null) {
            nodeTypesWithTemplates = new LinkedList<String>();

            Node templates = getNodeByPath(Constants.CONFIG_TEMPLATES);
            if (templates == null)
                return false;

            NodeIterator ni = templates.getNodes();
            while (ni.hasNext()) {
                nodeTypesWithTemplates.add(ni.nextNode().getName());
            }
        }

        for (String nodeType : nodeTypesWithTemplates)
            if (node.isNodeType(nodeType))
                return true;

        return false;
    }
}
